package ar.edu.itba.it.cg.grupo01.impl;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

import ar.edu.itba.it.cg.grupo01.api.image.Image;
import ar.edu.itba.it.cg.grupo01.impl.RayCaster.RayCasterOptions;
import ar.edu.itba.it.cg.grupo01.impl.color.ColorModeFactory;
import ar.edu.itba.it.cg.grupo01.impl.color.ColorVariationFactory;
import ar.edu.itba.it.cg.grupo01.impl.scene.SceneFactory;

/**
 * Hello world!
 * 
 */
public class RayCasterRunner {
	public static void main(final String[] args) {

		RayCasterRunner rayCasterRunner = new RayCasterRunner();
		rayCasterRunner.run(args);

	}

	private void run(String[] args) {

		ColorModeFactory colorModeFactory = new ColorModeFactory(new Random(System.nanoTime()));
		ColorVariationFactory colorVariationFactory = new ColorVariationFactory();

		Options options = buildOptions();

		// GNU parser is used as options have more than one character
		CommandLineParser parser = new GnuParser();
		CommandLine line;
		try {
			// parse the command line arguments
			line = parser.parse(options, args);
		} catch (ParseException exp) {
			// oops, something went wrong
			System.err.println("Parsing failed.  Reason: " + exp.getMessage());
			printHelp(options);
			return;
		}

		if (line.hasOption("help")) {
			printHelp(options);
			return;
		}

		if (!line.hasOption("i")) {
			System.err
					.println("Parsing failed.  Reason: Missing required option: i");
			return;
		}

		RayCasterOptions rayCasterOptions;
		try {
			rayCasterOptions = buildRayCasterOptions(line, colorModeFactory,
					colorVariationFactory);
		} catch (ParseException e) {
			System.err.println(e.getMessage());
			return;
		}

		run(rayCasterOptions);

	}

	private void run(RayCasterOptions rayCasterOptions) {
		long startTime = System.nanoTime();

		RayCaster rayCaster = new RayCaster(CameraFactory.getInstance(),
				new SceneFactory(new IntersectionFactory()));
		Image image = rayCaster.drawImage(rayCasterOptions,
				BufferedImage.TYPE_INT_RGB);

		BufferedImage bi = image.getBufferedImage();

		String outputFileName = rayCasterOptions.getOutputFile();

		File out = new File(outputFileName);

		String[] formatHolder = outputFileName.split("\\.");

		try {
			ImageIO.write(bi, formatHolder[1], out);
		} catch (IOException e) {
			System.err
					.println("Something bad happened when trying to save the image: "
							+ e.getMessage());
		}

		long ellapsedTime = System.nanoTime() - startTime;
		if (rayCasterOptions.isShowTime()) {
			System.out.println("Time taken: " + (ellapsedTime / 1E9)
					+ " seconds");
		}

	}

	private RayCasterOptions buildRayCasterOptions(CommandLine line,
			ColorModeFactory colorModeFactory,
			ColorVariationFactory colorVariationFactory) throws ParseException {

		// TODO sacar los ifs de la muerte

		String sceneName = line.getOptionValue("i");

		if (!sceneName.matches("scene\\d\\.sc")) {
			throw new ParseException(
					"Scene name must be in the form sceneX.sc where X is a number. Got: "
							+ sceneName);
		}

		RayCasterOptions rayCasterOptions = new RayCasterOptions(sceneName,
				colorModeFactory.forName("random"), colorVariationFactory
						.forName("linear"));

		if (line.hasOption("o")) {
			String outputFile = line.getOptionValue("o");
			if (!outputFile.matches("[a-zA-Z0-9_]+\\.(png|bmp)")) {
				throw new ParseException(
						"Output file must letters and numbers only and be in bmp or png format. Got: "
								+ outputFile);
			}

			rayCasterOptions.setOutputFile(line.getOptionValue("o"));
		}

		if (line.hasOption("time")) {
			rayCasterOptions.setShowTime(true);
		}

		if (line.hasOption("fov")) {
			String fovValue = line.getOptionValue("fov");
			try {
				double fov = Double.valueOf(fovValue);
				if (fov < 0.0 || fov > 360.0) {
					throw new ParseException(
							"Fov should be between 0.0 and 360.0. Got: "
									+ fovValue);
				}

				rayCasterOptions.setFov(fov);
			} catch (NumberFormatException e) {
				throw new ParseException("Fov should be a double value. Got: "
						+ fovValue);
			}
		}

		if (line.hasOption("size")) {
			String sizeValue = line.getOptionValue("size");
			String[] wAndH = sizeValue.split("x");

			if (wAndH.length != 2) {
				throw new ParseException(
						"Size should be in the format wxh [e.g. 640x480]");
			}

			try {
				int width = Integer.valueOf(wAndH[0]);
				int height = Integer.valueOf(wAndH[1]);

				if (width < 0 || height < 0) {
					throw new ParseException(
							"Width and height should be greater than 0. Got ["
									+ width + "x" + height + "]");
				}

				rayCasterOptions.setWidth(width);
				rayCasterOptions.setHeight(height);
			} catch (NumberFormatException e) {
				throw new ParseException(
						"The width and height should be positive integers. Got: "
								+ sizeValue);
			}
		}

		if (line.hasOption("cm")) {
			try {
				rayCasterOptions.setColorMode(colorModeFactory.forName(line
						.getOptionValue("cm")));
			} catch (IllegalArgumentException e) {
				throw new ParseException(e.getMessage());
			}
		}

		if (line.hasOption("cv")) {
			try {
				rayCasterOptions.setColorVariation(colorVariationFactory
						.forName(line.getOptionValue("cv")));
			} catch (IllegalArgumentException e) {
				throw new ParseException(e.getMessage());
			}
		}

		return rayCasterOptions;
	}

	private void printHelp(Options options) {
		HelpFormatter formatter = new HelpFormatter();
		formatter.printHelp("raytracer", options);
	}

	private Options buildOptions() {
		// create Options object
		Options options = new Options();

		// add help option (-help)
		options.addOption(new Option("help", "print this message"));
		// add time option (-time)
		options.addOption(new Option("time",
				"shows the time taken by the raycaster to generate the image"));
		// add input file option (-i)
		options.addOption(OptionBuilder.withArgName("scene").hasArg()
				.withDescription("input scene").create("i"));
		// add output file option (-o)
		options.addOption(OptionBuilder.withArgName("filename").hasArg()
				.withDescription("output image").create("o"));
		// add color mode option (-cm)
		options.addOption(OptionBuilder.withArgName("colorMode").hasArg()
				.withDescription("color mode [random(default) | ordered]")
				.create("cm"));
		options.addOption(OptionBuilder.withArgName("colorVariationMode")
				.hasArg().withDescription(
						"color variation mode [linear(default) | log]").create(
						"cv"));
		options.addOption(OptionBuilder.withArgName("fieldOfView").hasArg()
				.withDescription("field of view [default 60]").create("fov"));
		options.addOption(OptionBuilder.withArgName("imageSize").hasArg()
				.withDescription(
						"image size [<width>x<height> (default 640x480)]")
				.create("size"));

		return options;
	}

}
